﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XinChen
{
    class SerialNumber
    {
        //private char[] MapTable = { 'D', 'Q', 'N', 'F', 'X', 'A', 'E', 'G', 'R', 'J', 'V', 'S', 'H', 'M', 'C', 'O' ,
        //                                             '6', '9', '3', '7', '2', '4', 'K', 'W', 'Y', 'L', 'U', '5', '8', 'T', 'P', '0' };
        private string MapTable = "DQNFXAEGRJVSHMCO693724KWYLU58TP0";
        /// <summary>
        /// 序列号信息，建议创建一个实例重复使用，这样随机种子可以延续有效。
        /// </summary>
        public class SN_info
        {
            /// <summary>
            /// 随机种子，Tick加随机序列，保证每个序列号唯一
            /// </summary>
            public byte[] seed
            {
                get
                {
                    return Tools.CRC32.ComputeHash(DateTime.UtcNow.Ticks.ToString() + rnd.Next().ToString());
                    //return Tools.CRC32.ComputeHash("Debug Only");
                }
            }
            /// <summary>
            /// 软件编码，0-255
            /// </summary>
            public byte softwareID = 0;
            /// <summary>
            /// 功能控制码，4段序号时，最低1字节有效；5段序列号时，最低4字节有效。
            /// </summary>
            public long funtion = 0;
            /// <summary>
            /// 校验码，无实际作用，Debug only
            /// </summary>
            public byte[] CRC32 = new byte[4];

            /// <summary>
            /// 解码时返回是否有效序列号
            /// </summary>
            public bool Valid = false;
            /// <summary>
            /// 随机种子，影响seed，最好能实例化一个后持续使用，以获得连续随机值。
            /// </summary>
            public readonly Random rnd = new Random();
        }
        /// <summary>
        /// 创建5x5序列号，1字节产品代码，4字节功能代码
        /// </summary>
        /// <param name="_info">传入序列号结构，含产品代码和功能码</param>
        /// <returns>25位序列号字符串，每5位加空格</returns>
        public string Creat5(SN_info _info)
        {
            //Debug Only：排序检查MapTable有没重复字母
            //Array.Sort(MapTable);
            List<byte> sn = new List<byte>();

            sn.AddRange(_info.seed);//4字节，随机种子，保证序列号唯一
            sn.Add(_info.softwareID);//1字节，产品代码
            sn.AddRange(Tools.num2bin((int)(_info.funtion & 0xFFFFFFFF), 4));//4字节，功能控制
            _info.CRC32 = Tools.CRC32.ComputeHash(sn.ToArray());
            sn.AddRange(_info.CRC32);//4字节，校验码
            sn[sn.Count - 1] |= 0x0F;//最后一个字母要丢弃，先设为0x0F

            //从最后一位开始向前XOR
            for (int i = sn.Count - 1; i > 0; i--)
            {
                sn[i - 1] ^= sn[i];
            }

            //编码变换
            StringBuilder sn_str = new StringBuilder();
            int a;
            for (int i = 0; i < sn.Count; i++)
            {
                a = _info.rnd.Next(2);
                sn_str.Append(MapTable[sn[i] / 16 + a * 16]);
                sn_str.Append(MapTable[sn[i] % 16 + (1 - a) * 16]);//低位在后
            }

            //每5个字母插入空格
            int pos = 5;
            int count = sn_str.Length / 5 - 1;
            for (int i = 0; i < count; i++)
            {
                sn_str.Insert(pos, ' ');
                pos += 6;
            }

            //去掉最后一个字母“O”
            sn_str.Remove(sn_str.Length - 1, 1);

            return sn_str.ToString();
        }
        /// <summary>
        /// 创建5x4序列号，1字节产品代码，1字节功能代码
        /// </summary>
        /// <param name="_info">传入序列号结构，含产品代码和功能码</param>
        /// <returns>20位序列号字符串，每5位加空格</returns>
        public string Creat4(SN_info _info)
        {
            List<byte> sn = new List<byte>();

            sn.AddRange(_info.seed);//4字节，随机种子，保证序列号唯一
            sn.Add(_info.softwareID);//1字节，产品代码
            sn.Add((byte)(_info.funtion & 0xFF));//1字节，功能控制
            _info.CRC32 = Tools.CRC32.ComputeHash(sn.ToArray());
            sn.AddRange(_info.CRC32);//4字节，校验码

            //从最后一位开始向前XOR
            for (int i = sn.Count - 1; i > 0; i--)
            {
                sn[i - 1] ^= sn[i];
            }

            //编码变换
            StringBuilder sn_str = new StringBuilder();
            int a;
            for (int i = 0; i < sn.Count; i++)
            {
                a = _info.rnd.Next(2);
                sn_str.Append(MapTable[sn[i] / 16 + a * 16]);
                sn_str.Append(MapTable[sn[i] % 16 + (1 - a) * 16]);//低位在后
            }

            //每5个字母插入空格
            int pos = 5;
            int count = sn_str.Length / 5 - 1;
            for (int i = 0; i < count; i++)
            {
                sn_str.Insert(pos, ' ');
                pos += 6;
            }

            return sn_str.ToString();
        }

        /// <summary>
        /// 解码5x5序列号
        /// </summary>
        /// <param name="sn_org">可以含空格（29）或不含空格（25）</param>
        /// <returns>要注意判断Valid，是否有效序列号</returns>
        public SN_info Decode5(string sn_org)
        {
            SN_info _info = new SN_info();
            _info.Valid = false;

            //判断序列号字串长度是否合法
            string sn_str;
            if (sn_org.Length == 25)
            {
                sn_str = sn_org;
            }
            else if (sn_org.Length == 29)
            {
                sn_str = sn_org.Substring(0, 5) + sn_org.Substring(6, 5) + sn_org.Substring(12, 5) + sn_org.Substring(18, 5) + sn_org.Substring(24, 5) + "O";
            }
            else
            {
                return _info;
            }
            //判断字符是否在MapTable中
            for (int i = 0; i < sn_str.Length; i++)
            {
                if (MapTable.IndexOf(sn_str[i]) < 0) { return _info; }
            }

            //解码，如果出现不在MapTable的字符会被当做0x0F（-1&0x0F=0x0F）
            byte[] sn_bin = new byte[13];
            sn_bin[0] = (byte)(((MapTable.IndexOf(sn_str[0]) & 0x0F) << 4) + (MapTable.IndexOf(sn_str[1]) & 0x0F));
            for (int i = 1; i < sn_bin.Length; i++)
            {
                sn_bin[i] = (byte)(((MapTable.IndexOf(sn_str[i * 2]) & 0x0F) << 4) + (MapTable.IndexOf(sn_str[i * 2 + 1]) & 0x0F));
                sn_bin[i - 1] ^= sn_bin[i];
            }

            //校验CRC32
            _info.CRC32 = Tools.CRC32.ComputeHash(Tools.sub_bin(sn_bin, 0, 9));
            _info.CRC32[3] |= 0x0F;//最后一位编码前已变换为0x0F
            _info.Valid = true;
            for (int i = 0; i < _info.CRC32.Length; i++)
            {
                if (_info.CRC32[i] != sn_bin[9 + i])
                {
                    _info.Valid = false;
                    return _info;
                }
            }

            _info.softwareID = (byte)Tools.bin2num(sn_bin, 4, 1);
            _info.funtion = Tools.bin2num(sn_bin, 5, 4) & 0xFFFFFFFF;

            return _info;
        }
        /// <summary>
        /// 解码5x4序列号
        /// </summary>
        /// <param name="sn_org">可以含空格（29）或不含空格（25）</param>
        /// <returns>要注意判断Valid，是否有效序列号</returns>
        public SN_info Decode4(string sn_org)
        {
            SN_info _info = new SN_info();
            _info.Valid = false;

            //判断序列号字串长度是否合法
            string sn_str;
            if (sn_org.Length == 16)
            {
                sn_str = sn_org;
            }
            else if (sn_org.Length == 23)
            {
                sn_str = sn_org.Substring(0, 5) + sn_org.Substring(6, 5) + sn_org.Substring(12, 5) + sn_org.Substring(18, 5);
            }
            else
            {
                return _info;
            }
            //判断字符是否在MapTable中
            for (int i = 0; i < sn_str.Length; i++)
            {
                if (MapTable.IndexOf(sn_str[i]) < 0) { return _info; }
            }

            //解码，如果出现不在MapTable的字符会被当做0x0F（-1&0x0F=0x0F）
            byte[] sn_bin = new byte[10];
            sn_bin[0] = (byte)(((MapTable.IndexOf(sn_str[0]) & 0x0F) << 4) + (MapTable.IndexOf(sn_str[1]) & 0x0F));
            for (int i = 1; i < sn_bin.Length; i++)
            {
                sn_bin[i] = (byte)(((MapTable.IndexOf(sn_str[i * 2]) & 0x0F) << 4) + (MapTable.IndexOf(sn_str[i * 2 + 1]) & 0x0F));
                sn_bin[i - 1] ^= sn_bin[i];
            }

            //校验CRC32
            _info.CRC32 = Tools.CRC32.ComputeHash(Tools.sub_bin(sn_bin, 0, 6));
            _info.Valid = true;
            for (int i = 0; i < _info.CRC32.Length; i++)
            {
                if (_info.CRC32[i] != sn_bin[6 + i])
                {
                    _info.Valid = false;
                    return _info;
                }
            }

            _info.softwareID = (byte)Tools.bin2num(sn_bin, 4, 1);
            _info.funtion = Tools.bin2num(sn_bin, 5, 1) & 0xFF;

            return _info;
        }
    }
}
